/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Arg } from './arg';
import { Reg } from './reg';
import { locked, spill } from './symbols';

const STACKSLOT_SOME_VALUE_2: number = 2;
const STACKSLOT_SOME_VALUE_3: number = 3;
const STACKSLOT_SOME_VALUE_4: number = 4;
const STACKSLOT_SOME_VALUE_7: number = 7;
const STACKSLOT_SOME_VALUE_8: number = 8;

export class StackSlot {
  _index: number;
  _byteSize: number;
  _kind: string;
  _offsetFromFP: number | null = null;
  get offsetFromFP(): number | null {
    return this._offsetFromFP;
  }

  constructor(index: number, byteSize: number, kind: string) {
    this._index = index;
    this._byteSize = byteSize;
    this._kind = kind;
  }

  get byteSize(): number {
    return this._byteSize;
  }

  get kind(): string {
    return this._kind;
  }

  get isLocked(): boolean {
    return this._kind === locked;
  }

  get isSpill(): boolean {
    return this._kind === spill;
  }

  get index(): number {
    return this._index;
  }

  ensureSize(size: number): void {
    if (this.offsetFromFP !== null) {
      throw new Error('Stack slot already allocated');
    }
    this._byteSize = Math.max(this.byteSize, size);
  }

  get alignment(): number {
    if (this._byteSize <= 1) {
      return 1;
    }
    if (this._byteSize <= STACKSLOT_SOME_VALUE_2) {
      return STACKSLOT_SOME_VALUE_2;
    }
    if (this._byteSize <= STACKSLOT_SOME_VALUE_4) {
      return STACKSLOT_SOME_VALUE_4;
    }
    return STACKSLOT_SOME_VALUE_8;
  }

  setOffsetFromFP(value: number | null): void {
    this._offsetFromFP = value;
  }
  hash(): number {
    let value =
      (this.kind === spill ? 1 : 0) + this.byteSize * STACKSLOT_SOME_VALUE_3 + (this.offsetFromFP !== null ? this.offsetFromFP! * STACKSLOT_SOME_VALUE_7 : 0);
    return value >>> 0;
  }

  toString(): string {
    return (this.isSpill ? 'spill' : 'stack') + this._index + '<' + this._byteSize + (this._offsetFromFP ? ', offset = ' + this._offsetFromFP : '') + '>';
  }

  static extract(arg: Arg | Reg): StackSlot | null {
    let ar = arg as Arg;
    if (ar !== null && ar.isStack) {
      return ar.stackSlot();
    }
    return null;
  }

  static forEachFast(arg: Arg, fun: (StackSlot) => StackSlot | null): Arg | null {
    if (!arg.isStack) {
      return null;
    }
    let replacement = fun(arg.stackSlot());
    if (replacement !== null) {
      return Arg.createStack(replacement);
    }
    return null;
  }

  static forEach(
    arg: Arg,
    role: string,
    type: string,
    width: number,
    fun: (value: StackSlot, role: string, type: string, width: number) => StackSlot
  ): Arg | null {
    if (!arg.isStack) {
      return null;
    }

    let replacement = fun(arg.stackSlot(), role, type, width);
    if (replacement !== null) {
      return Arg.createStack(replacement);
    }
    return null;
  }
}
